# 6.3 Expressions

The Enjoy Template Engine's expression rules are generally in line with Java's expression rules, with only a few intuitive extensions aimed at improving the developer experience.

One key point to reiterate about expressions is that they are directly integrated with Java. Grasping this point will allow you to understand 90% of the template engine's usage. Here are some code examples:

123 + "abc"
"abcd".substring(0, 1)
userList.get(0).getName()
1
2
3

In the above code, the first and second lines are used exactly the same way as in Java expressions. In the third line, assuming userList contains User objects and that User has a getName() method, you can call an object's methods just like you would in Java, as long as you know the type of the variable.

# 1. Expressions With Basic Java Rules

  • Arithmetic operations: + - * / % ++ --
  • Comparison operations: > >= < <= == != (basic usage is the same, enhancements will be discussed later)
  • Logical operations: ! && ||
  • Ternary expression: ? :
  • Null constant: null
  • String constant: "jfinal club"
  • Boolean constant: true false
  • Numeric constant: 123 456F 789L 0.1D 0.2E10
  • Array access: array[i] (Maps are enhanced to support map[key])
  • Property access: object.field (Maps are enhanced to support map.key)
  • Method call: object.method(p1, p2…, pn) (Supports variable parameters)

Comma expression: 123, 1>2, null, "abc", 3+6 (The value of the comma expression is the value of the last expression)

Pro Tip: If a key in a map from the Java side is in Chinese, you can access it using map["中文"] instead of map.中文, as the engine will treat it as object.field, which by default doesn't support Chinese characters. You can enable it via Engine.setChineseExpression(true).

# 2. Property Access

Due to the frequent use of property access expressions in the template engine, intuitive extensions have been made. For example, with user.name:

  • If user.getName() exists, it takes precedence.
  • If user has a public name attribute, it will be used.
  • If user is a subclass of Model, user.get("name") will be called.
  • If user is a Record, user.get("name") will be called.
  • If user is a Map, user.get("name") will be called.

In addition, array length can be accessed via array.length, just like in Java.

Lastly, you can extend property access using the FieldGetter abstract class. For instance, the following configuration supports calling a boolean isGirl() method via the user.girl expression:

Engine.addFieldGetterToFirst(new com.jfinal.template.expr.ast.FieldGetters.IsMethodFieldGetter());
1

# 3. Method Calls

The template engine is designed to be directly integrated with Java, allowing you to call any public method on an object in the template. The rules are consistent with how you call methods in Java. For example:

#("ABCDE".substring(0, 3))
#(girl.getAge())
#(list.size())
#(map.get(key))
1
2
3
4

In the first line, the substring(0, 3) method on the String object is called, and the output is "ABC". The second line can be called when the girl object has a getAge() method. The third line can be called assuming map is a Map type.

In short, you can directly call any public method that an object owns in the template expression. The method call supports variable parameters, e.g., obj.find(String sql, Object … args).

Since the object method call is directly integrated with Java, the learning curve is zero, and you immediately gain a powerful extension mechanism.

# 4. Static Property Access

Starting from JFinal 5.0.2, this expression is "disabled" by default and needs to be enabled with the following configuration:

engine.setStaticFieldExpression(true);
1

Static properties defined in Java code can be accessed in the template. For example:

#if(x.status == com.demo.common.model.Account::STATUS_LOCK_ID)
   <span>(Account Locked)</span>
#end
1
2
3

Here, the static property is accessed using the class name followed by a double colon and the static property name. This way, you can avoid using specific constant values in the template, making it easier to refactor code later.

Note that the property must be declared as public static to be accessible. Also, it doesn't have to be final.

If a static property is frequently used, you can add it as a shared object via addSharedObject(...) and then refer to it using the field expression, which saves some code. For example, you can first configure the shared object like this:

public void configEngine(Engine me) {
    me.addSharedObject("Account", new Account());
}
1
2
3

And then, you can use the field expression to replace the original static property access expression in the template:

#if(x.status == Account.STATUS_LOCK_ID)
   <span>(Account Locked)</span>
#end
1
2
3

# 5. Static Method Calls

Starting from JFinal 5.0.2, this expression is "disabled" by default and needs to be enabled with the following configuration:

engine.setStaticMethodExpression(true);
1

The Enjoy Template Engine can call static methods in a very simple manner. Here's an example:

#if(com.jfinal.kit.StrKit::isBlank(title))
   ....
#end
1
2
3

The usage is consistent with static property access; you just replace the static property name with the static method name and add a pair of parentheses and parameters: Class Name + :: + Method Name(Parameters). Static method calls support variable parameters. And like static properties, the methods being called must be public static.

If you find it cumbersome to write out the package name, you can add the method as a shared method using me.addSharedMethod(...), and then you can call it directly using the method name without even needing the class name.

Additionally, you can also call methods on static properties. For example:

(com.jfinal.MyKit::me).method(paras)
1

In the above code, you first use a pair of parentheses to enclose the static property access expression and then call its method. The parentheses here are only to change the priority of the expression.

# 6. Optional Chaining Operator ?. (New in 5.0.0)

JFinal 5.0.0 introduced the optional chain operator, which can be used as follows:

# When article is null, title is not accessed and null is returned
article?.title

# Can be used for method calls
article?.getTitle()

# Can be used for chained operations
page?.list?.size()

# Can be used after a method call to avoid exceptions if getList() returns null
page?.getList()?.size()
1
2
3
4
5
6
7
8
9
10
11

The optional chaining operator can be used when you're accessing a field or calling a method on an object that might be null, thereby preventing exceptions. Note that this operator will always return null if the object is null. If you need to return other default values, you can use the "null-coalescing safe access operator" discussed in the next section.

# 7. Null-Coalescing Safe Access Operator

The Enjoy Template Engine has introduced the null-coalescing operator from Swift and C#, with some very natural extensions. The expression symbol for this is two closely placed question marks: ??. Here's a code example:

seoTitle ?? "JFinal Community"
object.field ??
object.method() ??
1
2
3

The first line is equivalent in functionality to Swift, where if seoTitle is null, the whole expression takes the value of the following expression. The second line provides null-safe property access for object.field, meaning the expression won't throw an exception if object is null, and its value will be null.

The third line is similar to the second but uses a method call instead of property access. Here as well, if object is null, the expression will not throw an exception, and its value will be null.

Of course, null-coalescing and null-safety can be naturally mixed, as shown below:

object.field ?? "Default Value"
object.method() ?? value
1
2

In these lines, if the null-safe property or method call returns null, the entire expression will take the value from the variable after the ??.

Special Note: The ?? operator has higher precedence than arithmetic operators (+, -, *, /, %) but lower precedence than unary operators (!, ++, --). To forcibly change the precedence, use parentheses.

# 8. Single-Quoted Strings

Given that the Template Engine is often used for HTML applications, support for single-quoted strings has been added. For example:

<a href="/" class="#(menu == 'index' ? 'current' : 'normal')"
   Home
</a>
1
2
3

In the above code, single-quoted strings are used three times within a ternary expression. This is convenient because it can work in conjunction with outer double quotes, or vice versa.

This design is very beneficial when writing string expressions within existing single or double quotes in template files.

# 9. Enhanced Equality and Inequality Expressions

The equality (==) and inequality (!=) expressions perform a left.equals(right) comparison, so you can directly compare strings. For example:

#if(nickName == "james")
  ...
#end
1
2
3

Note: The Controller.keepPara(...) method will convert any data to String before passing it to the view layer. Hence, two Integer types that could be compared using the equality expression become uncomparable after keepPara(...), as it becomes a comparison between String and Integer.

# 10. Enhanced Boolean Expressions

Boolean expressions have been enhanced to reduce code input. The rules, in order of priority, are as follows:

  • null returns false
  • boolean types return their original value
  • Strings and any objects inheriting from CharSequence return length > 0
  • Everything else returns true

These rules can reduce the amount of code in templates. For example:

#if(user && user.id == x.userId)
  ...
#end
1
2
3

In this code, the user expression effectively replaces the Java expression user != null, thus reducing code. Of course, if you use the ?? operator, it can be even simpler: if (user.id ?? == x.userId)

# 11. Map Definition Expressions

The most useful scenario for defining a Map expression is to provide a very flexible way of passing parameters when calling methods or functions, especially when the names and numbers of parameters are not determined. Here is the basic usage:

#set(map = {k1:123, "k2":"abc", "k3":object})
#(map.k1)
#(map.k2)
#(map["k1"])
#(map["k2"])
#(map.get("k1"))
1
2
3
4
5
6

In this example, a Map is defined using a pair of curly braces, with each element defined in the form of key: value. Multiple elements are separated by commas.

Keys can only be valid Java variable identifiers or String constant values. Note that using an identifier like k1 rather than a String constant like "k1" is just for convenience in writing; it's equivalent to a string and doesn't evaluate the identifier k1.

The code uses the #set directive to assign the defined variable to the map variable. The second and third lines use object.field for value access, while the fourth and fifth lines use map[key] for value access. The sixth line is consistent with Java expression usage.

Special Note: In the above code, if you try to access the value using map[k1], k1 will first be evaluated, resulting in null. Therefore, the code uses map["k1"] to access the value.

Additionally, Map retrieval also supports value retrieval during definition:

#({1:'Buy for yourself', 2:'Follow purchase'}.get(1))
#({1:'Buy for yourself', 2:'Follow purchase'}[2])

### Use with double question marks to support default values
#({1:'Buy for yourself', 2:'Follow purchase'}.get(999) ?? 'Others')
1
2
3
4
5

The keys here are int constants, supported from JFinal version 3.4.

# 12. Array Definition Expressions

Let's go straight to an example:

// Define the array 'array' and assign default values to its elements
#set(array = [123, "abc", true])

// Get the value at index 1, output: "abc"
#(array[1])

// Assign 'false' to the element at index 1 and output it
#(array[1] = false, array[1])
1
2
3
4
5
6
7
8

This code demonstrates array definition, initialization, data retrieval, and assignment. The last line does not use the #set directive, meaning array definition expressions can be used in any directive internally (Map definition expressions can do the same).

Array definition expressions can use any expression for element initialization, including variables, method call return values, etc.:

#set(array = [123, "abc", true, a && b || c, 1 + 2, obj.doIt(x)])
1

# 13. Range Array Definition Expressions

Let's go straight to an example:

#for(x : [1..10])
   #(x)
#end
1
2
3

In this example, the expression [1..10] defines a range array containing integers from 1 to 10. This is typically useful for frontend development, where you might want to iterate over a set of static data without having to retrieve it from the backend.

Additionally, descending range arrays are also supported, like [10..1], which will define an array of integers from 10 down to 1.

# 14. Comma Expressions

An expression that combines multiple expressions separated by commas is known as a comma expression. The value of a comma expression is the value of the last expression in the sequence. For example, the value of the comma expression 1+2, 3*4 is 12.

# 15. Removed Operators from Java

Considering the use-cases for template engines, bitwise operators have been removed to avoid complexity and to keep the template engine straightforward, thus also potentially improving performance.

# 16. Summary of Expressions

The various expression capabilities described above are primarily extensions built on top of Java expression rules for enhanced development experience. You can also ignore these specific capabilities and use them as you would Java expressions, thus eliminating the learning curve.

The aforementioned extensions built on top of Java expression rules serve two main purposes:

  1. They are added based on actual use-cases of template engines, such as single-quoted strings.
  2. They are improvements over verbose Java syntax. For example, string comparisons like str == "james" replace str.equals("james").

Therefore, these extensions are both valuable and necessary.

Last Updated: 9/22/2023, 4:50:57 AM